Understanding Lambda Expressions in Java
Course: Core Java Training
Topic: Understanding Lambda Expressions
Course Link: Core Java Training
Demo Link: Register for a Free Course Demo
Website: JustAcademy
1. Introduction to Lambda Expressions
Lambda expressions are one of the important features introduced in Java 8. They provide a concise way to represent a block of code that can be passed as an argument to a method or stored in a variable. Lambda expressions are mainly used to implement functional interfaces and simplify code that uses anonymous inner classes.
Before Java 8, developers often needed to create anonymous classes to provide implementations for interfaces containing a single abstract method. Lambda expressions reduce this boilerplate code and make programs easier to read and maintain.
Lambda expressions are widely used with collections, event handling, sorting, filtering, and the Java Stream API.
Example: Traditional Approach
interface Greeting {
void sayHello();
}
public class Main {
public static void main(String[] args) {
Greeting greeting = new Greeting() {
@Override
public void sayHello() {
System.out.println("Hello, Java!");
}
};
greeting.sayHello();
}
}
Example: Using a Lambda Expression
interface Greeting {
void sayHello();
}
public class Main {
public static void main(String[] args) {
Greeting greeting = () -> {
System.out.println("Hello, Java!");
};
greeting.sayHello();
}
}
Both examples produce the same output, but the lambda expression uses less code.
2. Learning Objectives
- Understand the meaning and purpose of lambda expressions.
- Learn the syntax of lambda expressions.
- Understand the relationship between lambda expressions and functional interfaces.
- Write lambda expressions with zero, one, and multiple parameters.
- Use lambda expressions with collections and sorting operations.
- Explore lambda expressions with the Stream API.
- Identify common mistakes and follow best practices.
3. What Is a Lambda Expression?
A lambda expression is an anonymous function that contains parameters, an arrow operator, and a body. It does not have a conventional method name or an explicitly declared return type. Java determines the target functional interface from the context in which the lambda expression is used.
A lambda expression is not a standalone method declaration. It provides an implementation of the single abstract method of a compatible functional interface.
Basic Syntax
(parameters) -> expression
For a block containing multiple statements, braces are used:
(parameters) -> {
// Statements
}
Components of a Lambda Expression
| Component |
Description |
Example |
| Parameters |
Inputs accepted by the lambda expression. |
(a, b) |
| Arrow operator |
Separates the parameters from the body. |
-> |
| Body |
Contains the expression or statements to execute. |
a + b |
| Expression body |
Contains a single expression. |
(a, b) -> a + b |
| Block body |
Contains one or more statements inside braces. |
x -> { return x * 2; } |
4. Understanding Functional Interfaces
A functional interface is an interface that has exactly one abstract method. It is the primary target type for lambda expressions. A functional interface may also contain default methods, static methods, and methods corresponding to public methods of Object without violating this rule.
The @FunctionalInterface annotation is optional, but it is recommended because the compiler checks that the interface satisfies the functional-interface requirements.
Example
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
public class Main {
public static void main(String[] args) {
Calculator addition = (a, b) -> a + b;
Calculator multiplication = (a, b) -> a * b;
System.out.println(addition.calculate(10, 5));
System.out.println(multiplication.calculate(10, 5));
}
}
Output:
15
50
Here, the Calculator interface contains one abstract method, calculate(). The two lambda expressions provide different implementations of that method.
5. Different Types of Lambda Expressions
5.1 Lambda Expression Without Parameters
When a functional interface method does not accept parameters, empty parentheses are used.
@FunctionalInterface
interface Message {
void display();
}
public class Main {
public static void main(String[] args) {
Message message = () -> System.out.println("Welcome to Java 8");
message.display();
}
}
Output:
Welcome to Java 8
5.2 Lambda Expression With One Parameter
When there is one parameter, parentheses around the parameter can be omitted if its type is not explicitly declared.
@FunctionalInterface
interface Square {
int calculate(int number);
}
public class Main {
public static void main(String[] args) {
Square square = number -> number * number;
System.out.println(square.calculate(6));
}
}
Output:
36
5.3 Lambda Expression With Multiple Parameters
Multiple parameters are separated by commas and enclosed in parentheses.
@FunctionalInterface
interface Subtraction {
int subtract(int a, int b);
}
public class Main {
public static void main(String[] args) {
Subtraction operation = (a, b) -> a - b;
System.out.println(operation.subtract(20, 8));
}
}
Output:
12
5.4 Lambda Expression With Multiple Statements
When the lambda body contains several statements, use curly braces. If the functional method returns a value, an explicit return statement is needed in a block body.
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
public class Main {
public static void main(String[] args) {
Calculator operation = (a, b) -> {
System.out.println("Performing addition");
int result = a + b;
return result;
};
System.out.println(operation.calculate(12, 8));
}
}
Output:
Performing addition
20
6. Type Inference in Lambda Expressions
Type inference means that the Java compiler can determine parameter types from the target functional interface. In many situations, you do not need to specify the parameter types explicitly.
Example Without Explicit Types
Calculator addition = (a, b) -> a + b;
Example With Explicit Types
Calculator addition = (int a, int b) -> a + b;
Both examples are valid when Calculator defines a compatible method that accepts two integers and returns an integer.
When explicitly declaring parameter types, all parameters in the same lambda expression must have explicit types. You cannot mix explicitly typed and implicitly typed parameters.
7. Expression Body vs Block Body
| Feature |
Expression Body |
Block Body |
| Syntax |
x -> x * 2 |
x -> { return x * 2; } |
| Statements |
Single expression |
One or more statements |
| Return statement |
Not required for a value-producing expression |
Required when returning a value explicitly |
| Readability |
Best for short operations |
Best for more complex logic |
Example
interface NumberOperation {
int process(int number);
}
public class Main {
public static void main(String[] args) {
NumberOperation simple = n -> n * 3;
NumberOperation detailed = n -> {
int result = n * 3;
System.out.println("Calculated result: " + result);
return result;
};
System.out.println(simple.process(4));
System.out.println(detailed.process(4));
}
}
8. Built-in Functional Interfaces in Java
Java provides several functional interfaces in the java.util.function package. These interfaces help developers use lambda expressions without defining a custom interface for every operation.
| Interface |
Purpose |
Abstract Method |
Example |
Predicate<T> |
Tests a condition and returns a boolean. |
test() |
n -> n > 10 |
Function<T,R> |
Transforms an input into an output. |
apply() |
s -> s.length() |
Consumer<T> |
Accepts an input and performs an action. |
accept() |
s -> System.out.println(s) |
Supplier<T> |
Supplies a value without an input parameter. |
get() |
() -> "Hello" |
UnaryOperator<T> |
Transforms a value into another value of the same type. |
apply() |
n -> n * 2 |
BinaryOperator<T> |
Combines two values of the same type into one value of that type. |
apply() |
(a, b) -> a + b |
Example Using Predicate
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate isAdultAge = age -> age >= 18;
System.out.println(isAdultAge.test(20));
System.out.println(isAdultAge.test(15));
}
}
Output:
true
false
Example Using Function
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function getLength = text -> text.length();
System.out.println(getLength.apply("Java"));
}
}
Output:
4
Example Using Consumer
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer printer = message ->
System.out.println(message);
printer.accept("Learning Lambda Expressions");
}
}
Output:
Learning Lambda Expressions
Example Using Supplier
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Supplier messageSupplier =
() -> "Welcome to Core Java";
System.out.println(messageSupplier.get());
}
}
Output:
Welcome to Core Java
9. Using Lambda Expressions With Collections
Lambda expressions simplify operations on collections such as lists and sets. They can be used with methods such as forEach() to process each element.
Example: Displaying List Elements
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> languages =
Arrays.asList("Java", "Python", "JavaScript");
languages.forEach(language ->
System.out.println(language));
}
}
Output:
Java
Python
JavaScript
Example: Sorting a List
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names =
Arrays.asList("Rahul", "Amit", "Priya", "Neha");
names.sort((a, b) -> a.compareTo(b));
names.forEach(System.out::println);
}
}
Output:
Amit
Neha
Priya
Rahul
The lambda expression provides the comparison logic used by the sorting operation. The final line uses a method reference, which is a concise alternative for certain lambda expressions.
10. Using Lambda Expressions With the Stream API
The Stream API, introduced in Java 8, allows developers to process collections through operations such as filtering, mapping, sorting, and collecting. Lambda expressions commonly provide the logic for these operations.
Example: Filtering Numbers
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(10, 15, 20, 25, 30, 35);
List<Integer> evenNumbers = numbers.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers);
}
}
Output:
[10, 20, 30]
Example: Transforming Values With map()
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.map(number -> number * number)
.collect(Collectors.toList());
System.out.println(squares);
}
}
Output:
[1, 4, 9, 16, 25]
Example: Finding Values That Match a Condition
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names =
Arrays.asList("Amit", "Anita", "Rahul", "Ankur");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(name -> System.out.println(name));
}
}
Output:
Amit
Anita
Ankur
11. Lambda Expressions vs Anonymous Inner Classes
Both lambda expressions and anonymous inner classes can provide implementations of interfaces, but they are not identical.
| Feature |
Lambda Expression |
Anonymous Inner Class |
| Syntax |
Compact and concise |
More verbose |
| Primary use |
Functional interfaces |
Implementing interfaces or extending classes |
| Abstract methods |
Targets an interface with one abstract method |
Can implement interfaces with multiple abstract methods |
this reference |
Refers to the enclosing object |
Refers to the anonymous class instance |
| State and methods |
Cannot declare additional instance methods as a separate class body |
Can declare fields and additional methods within the class body |
Example: Difference in the Meaning of this
public class Main {
private String name = "Outer Class";
public void demonstrate() {
Runnable task = () -> {
System.out.println(this.name);
};
task.run();
}
public static void main(String[] args) {
new Main().demonstrate();
}
}
Output:
Outer Class
In a lambda expression, this refers to the enclosing Main object. In an anonymous inner class, this would refer to the anonymous class instance.
12. Variable Scope and Effectively Final Variables
A lambda expression can access local variables from its enclosing scope when those variables are final or effectively final. A variable is effectively final when it is assigned only once and is not reassigned afterward.
Valid Example
public class Main {
public static void main(String[] args) {
int bonus = 100;
Runnable task = () ->
System.out.println("Bonus: " + bonus);
task.run();
}
}
Output:
Bonus: 100
Invalid Example
public class Main {
public static void main(String[] args) {
int bonus = 100;
Runnable task = () ->
System.out.println(bonus);
bonus = 200; // Compilation error
task.run();
}
}
The code fails to compile because bonus is captured by the lambda expression and then reassigned. A captured local variable must be final or effectively final.
Instance fields can be modified inside a lambda expression, subject to normal access and concurrency rules. However, modifying shared mutable state from parallel streams or concurrent tasks can produce unexpected results.
13. Method References and Lambda Expressions
Method references are a related Java 8 feature that can make certain lambda expressions shorter when the lambda simply calls an existing method.
| Lambda Expression |
Equivalent Method Reference |
name -> System.out.println(name) |
System.out::println |
text -> text.toUpperCase() |
String::toUpperCase |
() -> new ArrayList<String>() |
ArrayList<String>::new |
Example
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names =
Arrays.asList("Amit", "Priya", "Rahul");
names.forEach(System.out::println);
}
}
Method references are useful when they improve readability. If the operation requires additional logic, a normal lambda expression may be clearer.
14. Advantages of Lambda Expressions
- Less boilerplate: Reduces repetitive code compared with many anonymous inner classes.
- Improved readability: Expresses short operations in a compact form.
- Functional programming support: Encourages passing behavior as an argument.
- Collection processing: Works naturally with collection methods such as
forEach().
- Stream API integration: Supports filtering, mapping, sorting, and reduction.
- Reusable functional logic: Lambda expressions can be assigned to compatible functional-interface variables.
- Flexible APIs: Makes it easier to design methods that accept custom behavior.
15. Limitations and Common Mistakes
| Mistake |
Explanation |
Correct Approach |
| Using an incompatible interface |
A lambda cannot directly target an interface that has multiple unrelated abstract methods. |
Use a compatible functional interface. |
| Incorrect parameter syntax |
Mixing explicitly typed and implicitly typed parameters is invalid. |
Declare types for all parameters or omit them all. |
| Missing return statement in a block body |
A block body that must return a value needs a compatible return statement on every applicable path. |
Return the expected result. |
| Reassigning captured local variables |
Captured local variables must be final or effectively final. |
Avoid reassigning captured variables. |
| Overcomplicating a lambda |
Long, nested logic can be difficult to understand. |
Extract complex logic into a named method. |
| Unintended side effects |
Changing shared state may cause unexpected behavior, particularly in parallel processing. |
Prefer stateless operations where practical. |
16. Best Practices for Writing Lambda Expressions
- Use short expression bodies for simple calculations and transformations.
- Use block bodies only when multiple statements improve clarity.
- Prefer descriptive parameter names when they make the logic easier to understand.
- Use built-in functional interfaces when they suit the required operation.
- Use the
@FunctionalInterface annotation for custom functional interfaces when appropriate.
- Prefer method references when they are clearer than equivalent lambda expressions.
- Avoid modifying shared state inside stream operations.
- Extract complicated business logic into named methods for easier testing and maintenance.
- Compile and test lambda expressions with representative input values.
- Remember that lambda expressions implement behavior; they do not automatically make code asynchronous or parallel.
17. Practical Project: Employee Salary Processing
Objective: Use lambda expressions to filter employees whose salaries exceed a specified amount and display their details.
Program
import java.util.Arrays;
import java.util.List;
class Employee {
private final String name;
private final double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
}
public class Main {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Amit", 30000),
new Employee("Priya", 45000),
new Employee("Rahul", 60000),
new Employee("Neha", 38000)
);
employees.stream()
.filter(employee -> employee.getSalary() > 40000)
.forEach(employee ->
System.out.println(
employee.getName() + " - " +
employee.getSalary()
)
);
}
}
Output:
Priya - 45000.0
Rahul - 60000.0
Project Explanation
- The
Employee class represents an employee with a name and salary.
- The program creates a list of employee objects.
- The
stream() method creates a stream for processing the employees.
- The
filter() method uses a lambda expression to select employees earning more than 40,000.
- The
forEach() method uses another lambda expression to print each matching employee's details.
Suggested Project Enhancements
- Sort employees by salary using a lambda expression.
- Filter employees by department or experience.
- Calculate the average salary using stream operations.
- Convert employee names to uppercase using
map().
- Collect matching employees into a separate list.
18. Practice Exercises
- Create a functional interface named
Multiplication with a method that multiplies two integers. Implement it using a lambda expression.
- Write a lambda expression that checks whether a number is positive.
- Use a lambda expression to print every element of a list of cities.
- Sort a list of integers in ascending order using a lambda expression.
- Use
Predicate<Integer> to check whether a number is divisible by five.
- Use
Function<String, Integer> to calculate the length of a string.
- Use the Stream API to filter numbers greater than 50 from a list.
- Create an employee list and display employees earning above a chosen salary threshold.
19. Interview Questions and Answers
Q1. What is a lambda expression in Java?
A lambda expression is a concise way to represent behavior that implements the single abstract method of a compatible functional interface.
Q2. Which version of Java introduced lambda expressions?
Lambda expressions were introduced in Java 8.
Q3. What is a functional interface?
A functional interface has exactly one abstract method. It can also contain default and static methods.
Q4. What does the arrow operator do?
The -> operator separates the parameter list from the body of the lambda expression.
Q5. Can a lambda expression have no parameters?
Yes. Empty parentheses are used, as in () -> System.out.println("Hello").
Q6. Can a lambda expression contain multiple statements?
Yes. Use a block body enclosed in curly braces. If a value must be returned, include a suitable return statement.
Q7. What is type inference in lambda expressions?
Type inference allows the compiler to determine parameter types from the target functional interface in the surrounding context.
Q8. Can a lambda expression implement an interface with multiple abstract methods?
No, not directly. A lambda expression requires a functional-interface target with one abstract method.
Q9. What are effectively final variables?
They are local variables that are not explicitly declared final but are never reassigned after initialization. They can be captured by lambda expressions.
Q10. What is the difference between a lambda expression and a method reference?
A lambda expression describes behavior using an expression or block, while a method reference is a shorthand for certain lambdas that call an existing method or constructor.
Q11. How are lambda expressions used with streams?
They provide the logic for operations such as filter(), map(), sorted(), and forEach().
Q12. Do lambda expressions automatically execute code in parallel?
No. Lambda expressions do not automatically create parallel execution. Parallelism depends on the API or execution mechanism using the lambda.
20. Quick Revision Notes
- Lambda expressions were introduced in Java 8.
- The basic syntax is
(parameters) -> expression.
- Lambda expressions commonly target functional interfaces.
- Functional interfaces have exactly one abstract method.
- Parameter types can often be inferred by the compiler.
- Use braces for multi-statement lambda bodies.
- Captured local variables must be final or effectively final.
- Common functional interfaces include
Predicate, Function, Consumer, and Supplier.
- Lambda expressions are frequently used with collections and the Stream API.
- Method references can simplify lambdas that call existing methods.
21. Conclusion
Lambda expressions are an essential feature of modern Java programming. They reduce boilerplate code, support functional programming techniques, and make collection processing more expressive. Understanding lambda syntax, functional interfaces, type inference, variable scope, and Stream API operations helps developers write cleaner and more maintainable Core Java applications.
Practice with small examples before applying lambda expressions to larger projects. Learn when a concise lambda improves readability and when a named method is a better choice.
Learn Core Java With JustAcademy
Strengthen your Java programming fundamentals and explore important concepts such as object-oriented programming, collections, exception handling, file I/O, and Java 8 features.
Explore the course: Core Java Training at JustAcademy
Register for a demo: Register for a Course Demo
Visit the website: www.justacademy.co